/* Actual implemenation of ENCx24J600 driver */

/* Copyright (C) 2010-2011 David Zanetti
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/* Datasheet:
 * http://www.microchip.com/wwwproducts/Devices.aspx?dDocName=en542414
 */

/* Register map */

/* These are all 8-bit mode */

#define ETXSTL 		0x7e00
#define ETXSTH 		0x7e01
#define ETXLENL 	0x7e02
#define ETXLENH		0x7e03
#define ERXSTL		0x7e04
#define ERXSTH		0x7e05
#define ERXTAILL	0x7e06
#define ERXTAILH	0x7e07
#define ERXHEADL	0x7e08
#define	ERXHEADH	0x7e09
#define EDMASTL		0x7e0a
#define EDMASTH		0x7e0b
#define EDMALENL	0x7e0c
#define EDMALENH	0x7e0d
#define EDMADSTL	0x7e0e
#define EDMADSTH	0x7e0f
#define EDMACSL		0x7e10
#define EDMACSH		0x7e11
#define ETXSTATL	0x7e12
#define ETXSTATH	0x7e13
#define ETXWIREL	0x7e14
#define ETXWIREH	0x7e15
#define EUDASTL		0x7e16
#define EUDASTH		0x7e17
#define EUDANDL		0x7e18
#define EUDANDH		0x7e19
#define ESTATL		0x7e1a
#define ESTATH		0x7e1b
#define EIRL		0x7e1c
#define EIRH		0x7e1d
#define ECON1L		0x7e1e
#define ECON1H		0x7e1f
/* bank 2 */
#define EHT1L		0x7e20
#define EHT1H		0x7e21
#define EHT2L		0x7e22
#define EHT2H		0x7e23
#define EHT3L		0x7e24
#define EHT3H		0x7e25
#define EHT4L		0x7e26
#define EHT4H		0x7e27
#define EPMM1L		0x7e28
#define EPMM1H		0x7e29
#define EPMM2L		0x7e2a
#define EPPM2H		0x7e2b
#define EPMM3L		0x7e2c
#define EPMM3H		0x7e2d
#define EPMM4L		0x7e2e
#define EPMM4H		0x7e2f
#define EPMCSL		0x7e30
#define EPMCSH		0x7e31
#define EPMOL		0x7e32
#define EPMOH		0x7e33
#define ERXFCONL	0x7e34
#define ERXFCONH	0x7e35
/* 0x7e36 - 0x7e3f is dupe of 0x7e16 - 0x7e1f */
/* bank 3 */
#define MACON1L 	0x7e40
#define MACON1H		0x7e41
#define MACON2L		0x7e42
#define MACON2H		0x7e43
#define MABBIPGL	0x7e44
#define MABBIPGH	0x7e45
#define MAIPGL		0x7e46
#define MAIPGH		0x7e47
#define MACLCONL	0x7e48
#define MACLCONH	0x7e49
#define MAMXFLL		0x7e4a
#define MAMXFLH		0x7e4b
/* 0x7e4c - 0x7e51 reserved */
#define MICMDL		0x7e52
#define MICMDH		0x7e53
#define MIREGADRL	0x7e54
#define MIREGADRH	0x7e55
/* 0x7e56 - 0x7e5f is dupe id 0x7e16 - 0x7e1f */
/* bank 4 */
#define MAADR3L		0x7e60
#define MAADR3H		0x7e61
#define MAADR2L		0x7e62
#define MAADR2H		0x7e63
#define MAADR1L		0x7e64
#define MAADR1H		0x7e65
#define MIWRL		0x7e66
#define MIWRH		0x7e67
#define MIRDL		0x7e68
#define MIRDH		0x7e69
#define MISTATL		0x7e6a
#define MISTATH		0x7e6b
#define EPAUSL		0x7e6c
#define EPAUSH		0x7e6d	
#define ECON2L		0x7e6e
#define ECON2H		0x7e6f
#define ERXWML		0x7e70
#define ERXWMH		0x7e71
#define EIEL		0x7e72
#define EIEH		0x7e73
#define EIDLEDL		0x7e74
#define EIDLEDH		0x7e75
/* 0x7e76 - 0x7e7f is dupe of 0x7e16 - 0x7e1f */;
/* bank 5 */
#define EGPDATA		0x7e80
/* 0x7e81 reserved */
#define ERXDATA		0x7e82
/* 0x7e83 reserved */
#define EUDADATA	0x7e84
/* 0x7e85 reserved */
#define EGPRDPTL	0x7e86
#define EGPRDPTH	0x7e87
#define EGPWRPTL	0x7e88
#define EGPWRPTH	0x7e89
#define ERXRDPTL	0x7e8a
#define ERXRDPTH	0x7e8b
#define ERXWRPTL	0x7e8c
#define ERXWRPTH	0x7e8d
#define EUDARDPTL	0x7e8e
#define EUDARDPTH	0x7e8f
#define EUDAWRPTL	0x7e90
#define EUDAWRPTH	0x7e91
/* 0x7e92 - 0x7e9d reserved */
/* 0x7e9e - 0x7e9f undefined */

/* offset for a bitset write */
#define BITSET		0x100
#define BITCLR		0x180

/* attributes - 8-bit mode, value correct for either L or H byte */
/* ESTAT */
#define INT		0x80
#define FCIDLE		0x40
#define RXBUSY		0x20
#define CLKRDY		0x10
#define PHYDPX		0x04
#define PHYLNK		0x01	
/* EIR */
#define CRYPTEN 	0x80
#define MODEXIF		0x40
#define HASHIF		0x20
#define AESIF		0x10
#define LINKIF		0x08
#define PKTIF		0x40
#define DMAIF		0x20
#define TXIF		0x08
#define TXABTIF		0x04
#define RXABTIF		0x02
#define PCFULIF		0x01
/* ECON1 */
#define MODEXST		0x80
#define HASHEN		0x40
#define HASHOP		0x20
#define HASHLST		0x10
#define AESST		0x08
#define AESOP1		0x04
#define AESOP0		0x02
#define PKTDEC		0x01
#define FCOP1		0x80
#define FCOP0		0x40
#define DMAST		0x20
#define DMACPY		0x10
#define DMACSSD		0x08
#define DMANOCS		0x04
#define TXRTS		0x02
#define RXEN		0x01
/* ETXSTAT */
#define LATECOL		0x04
#define MAXCOL		0x02
#define EXDEFER		0x01
#define DEFER_ETXSTAT		0x80
#define CRCBAD		0x10
#define COLCNT3		0x08
#define COLCNT2		0x04
#define COLCNT1		0x02
#define COLCNT0		0x01
/* ERXFCON */
#define HTEN		0x80
#define MPEN		0x40
#define NOTPM		0x10
#define PMEN3		0x08
#define PMEN2		0x04
#define PMEN1		0x02
#define PMEN0		0x01
#define CRCEEN		0x80
#define CRCEN		0x40
#define RUNTEEN		0x20
#define RUNTEN		0x10
#define UCEN		0x08
#define NOTMEEN		0x04
#define MCEN		0x02
#define BCEN 		0x01
/* MACON1 */
#define LOOPBK		0x10
#define RXPAUS		0x04
#define PASSALL		0x02
/* MACON2 */
#define DEFER_MACON2		0x40
#define BPEN		0x20
#define NOBKOFF		0x10
#define PADCFG2		0x80
#define PADCFG1		0x40
#define PADCFG0		0x20
#define TXCRCEN		0x10
#define PHDREN		0x08
#define HFRMEN 		0x04
#define FULDPX		0x01
/* MABBIPG */
#define BBIPG6		0x40
#define BBIPG5          0x20
#define BBIPG4          0x10
#define BBIPG3          0x08
#define BBIPG2          0x04
#define BBIPG1          0x02
#define BBIPG0          0x01
/* MAIPG */
#define IPG6	        0x40
#define IPG5		0x20
#define IPG4         	0x10
#define IPG3         	0x08
#define IPG2         	0x04
#define IPG1         	0x02
#define IPG0         	0x01
/* MACLCON */
#define MAXRET3		0x08
#define MAXRET2         0x04
#define MAXRET1         0x02
#define MAXRET0         0x01
/* MICMD */
#define MIISCAN		0x02
#define MIIRD		0x01
/* MIREGADR */
#define PHREG4		0x10
#define PHREG3          0x08
#define PHREG2          0x04
#define PHREG1          0x02
#define PHREG0          0x01
/* MISTAT */
#define NVALID		0x04
#define SCAN		0x02
#define BUSY		0x01
/* ECON2 */
#define ETHEN		0x80
#define STRCH		0x40
#define TXMAC		0x20
#define SHA1MD5		0x10
#define COCON3		0x08
#define COCON2		0x04
#define COCON1		0x02
#define COCON0		0x01
#define AUTOFC		0x80
#define TXRST		0x40
#define RXRST		0x20
#define ETHRST		0x10
#define MODLEN1		0x08
#define MODLEN0		0x04
#define AESLEN1		0x02
#define AESLEN0		0x01
/* ERXWM */
#define RXFWM7		0x80
#define RXFWM6          0x40
#define RXFWM5          0x20
#define RXFWM4          0x10
#define RXFWM3          0x08
#define RXFWM2          0x04
#define RXFWM1          0x02
#define RXFWM0          0x01
#define RXEWM7          0x80
#define RXEWM6          0x40
#define RXEWM5          0x20
#define RXEWM4          0x10
#define RXEWM3          0x08
#define RXEWM2          0x04
#define RXEWM1          0x02
#define RXEWM0          0x01
/* EIE */
#define INTIE		0x80
#define MODEXIE		0x40
#define HASHIE		0x20
#define AESIE		0x10
#define LINKIE		0x08
#define PKTIE		0x40
#define DMAIE		0x20
#define TXIE		0x08
#define TXABTIE		0x04
#define RXABTIE		0x02
#define PCFULIE		0x01
/* EIDLED */
/* DEVID* here have a different name to remove conflict with XMEGA register names */
#define LACFG3		0x80
#define LACFG2          0x40
#define LACFG1          0x20
#define LACFG0          0x10
#define LBCFG3          0x08
#define LBCFG2          0x04
#define LBCFG1          0x02
#define LBCFG0          0x01
#define ENC_DEVID2		0x80
#define ENC_DEVID1		0x40
#define ENC_DEVID0		0x20
#define REVID4		0x10
#define REVID3		0x08
#define REVID2		0x04
#define REVID1		0x02
#define REVID0		0x01

/* PHY Register map - 5-bit addresses */

#define PHCON1		0x00
#define PHSTAT1		0x01
#define PHANA		0x04
#define PHANLPA		0x05
#define PHANE		0x06
#define PHCON2		0x11
#define PHSTAT2		0x1b
#define PHSTAT3		0x1f

/* PHY bits - 16-bit values */
/* PHY is always writen/read in 16-bit sizes */

/* PHCON1 */
#define PRST		0x8000
#define PLOOPBK		0x4000
#define SPD100		0x2000
#define ANEN		0x1000
#define PSLEEP		0x0800
#define RENEG		0x0200
#define PFULDPX		0x0100
/* PHSTAT1 */
#define FULL100		0x4000
#define HALF100		0x2000
#define FULL10		0x1000
#define HALF10		0x0800
#define ANDONE		0x0020
#define LRFAULT		0x0010
#define ANABLE		0x0008
#define LLSTAT		0x0004
#define EXTREGS		0x0001
/* PHANA */
#define ADNP		0x8000
#define ADFAULT		0x2000
#define ADPAUS1		0x0800
#define ADPAUS0		0x0400
#define AD100FD		0x0100
#define AD100		0x0080
#define AD10FD		0x0040
#define AD10 		0x0020
#define ADIEEE4		0x0010
#define ADIEEE3		0x0008
#define ADIEEE2		0x0004
#define ADIEEE1		0x0002
#define ADIEEE0		0x0001
/* PHANLPA */
#define LPNP		0x8000
#define LPACK		0x4000
#define LPFAULT		0x2000
#define LPPAUS1		0x0800
#define LPPAUS0		0x0400
#define LP100T4		0x0200
#define LP100FD		0x0100
#define LP100		0x0080
#define LP10FD		0x0040
#define LP10		0x0020
#define LPIEEE4		0x0010
#define LPIEEE3		0x0008
#define LPIEEE2		0x0004
#define LPIEEE1		0x0002
#define LPIEEE0		0x0001
/* PHANE */
#define PDFLT		0x0008
#define LPARCD		0x0002
#define LPANABL		0x0001
/* PHCON2 */
#define EDPWRDN		0x2000
#define EDTHRES		0x0800
#define FRCLNK		0x0004
#define EDSTAT		0x0002
/* PHSTAT2 */
#define PLRITY		0x0010
/* PHSTAT3 */
#define SPDDPX2		0x0010
#define SPDDPX1		0x0008
#define SPDDPX0		0x0004

/* Init constant, from section 8.1 */
#define RESET_CONST	0x1234

/* Size of the TX buffer */
/* 8kb. divided into four 2k slots */
#define TX_BUF_SIZE	0x2000
/* 2k slots */
#define TX_SLOT_SIZE    0x0800

/* ===================== */

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/eeprom.h>
#include <stdio.h>
#include <stdlib.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#include "global.h"
#include <util/delay.h>
#include "serial.h"
#include "encx24j600.h"

/* structure for TX slot status */
typedef struct {
		uint8_t busy; /* slot in use? */
		uint8_t ready; /* slot is ready for transmit */
		uint16_t length; /* total byte length, also acts as write head */
} tx_slot_t;

/* available TX slots in memory */
tx_slot_t tx_slot[4];

/* last slot transmitted. this is cached because status vector is global */
uint8_t last_tx;

/* current value */
uint8_t eth_linkmode;
uint8_t eth_linkstate;

/* saved in eeprom value */
uint8_t EEMEM eth_linkmode_eeprom;

/* rx next packet pointer, so we can quickly skip */
uint16_t next_packet_ptr = 0xffff;
/* rx current packet length */
uint16_t curr_rx_len = 0;
uint16_t curr_rx_len_left = 0;

void encx24j600_print_linkstate(void);
void encx24j600_phy_write(uint8_t addr, uint16_t value);
uint16_t encx24j600_phy_read(uint8_t addr);

void bus_init(void);

/* Read from the address given, return value read. No error reporting. */
//inline uint8_t __attribute__((always_inline)) bus_read(uint16_t addr);
//inline uint16_t __attribute__((always_inline)) bus_read16(uint16_t addr);
//inline void __attribute__((always_inline)) bus_read_block(uint16_t addr, void *buf, uint16_t len);

uint8_t  bus_read(uint16_t addr);
uint16_t  bus_read16(uint16_t addr);
void  bus_read_block(uint16_t addr, void *buf, uint16_t len);

/* Write to the address given. Never returns failure since there's no
   error reporting */
//inline void __attribute__((always_inline)) bus_write(uint16_t addr, uint8_t value);
//inline void __attribute__((always_inline)) bus_write16(uint16_t addr, uint16_t value);
//inline void __attribute__((always_inline)) bus_write_block(uint16_t addr, void *buf, uint16_t len);

void  bus_write(uint16_t addr, uint8_t value);
void  bus_write16(uint16_t addr, uint16_t value);
void  bus_write_block(uint16_t addr, void *buf, uint16_t len);

/* Interrupt line inbound from chip */
ISR(PORTB_INT0_vect) {
	/* only fired for !INT from chip */
	flag_ether |= FLAG_ETHER_INT;
}

/* Init */

void encx24j600_init(void) {
	uint8_t n;

	bus_init();

	/* Reset procedure, 8.1 */

	/* test psp interface */

	for (n = 0; n < 20; n++) {
		uint16_t value;

		bus_write(EUDASTL,(RESET_CONST & 0xff));
		bus_write(EUDASTH,(RESET_CONST >> 8));

		value = (bus_read(EUDASTL) | (bus_read(EUDASTH) << 8));

		if (value == RESET_CONST) {
			break;
		}

		_delay_ms(1000);
	}

	if (n == 20) {
		serial0_tx_PGM(PSTR("encx24j600: psp interface not ready, giving up\r\n"));
		return;
	}

#ifdef DEBUG_ENCX24J600
	serial0_tx_PGM(PSTR("encx24j600: psp interface ready\r\n"));
#endif

	/* Poll clkrdy */

	for (n = 0; n < 20; n++) {
		if (bus_read(ESTATH) & CLKRDY) {
			break;
		}
		_delay_ms(1000);
	}

	if (n == 20) {
		serial0_tx_PGM(PSTR("encx24j600: clkrdy timeout, giving up\r\n"));
		return;
	}

#ifdef DEBUG_ENCX24J600
	serial0_tx_PGM(PSTR("encx24j600: clkrdy good\r\n"));
#endif

#ifdef DEBUG_ENCX24J600_MEMORY

	/* Issue reset */
	bus_write(ECON2L+BITSET,ETHRST);
	_delay_ms(25);

	if ((bus_read(EUDASTL) | (bus_read(EUDASTH) << 8)) != (uint16_t) 0x0000) {
		serial0_tx_PGM(PSTR("encx24j600: reset failed, giving up\r\n"));
		return;
	}

	serial0_tx_PGM(PSTR("encx24j600: memory test\r\n"));
	{
		uint16_t n;
		uint8_t x, y;

		/* test register setting first */
		for (n = 0; n < 0x7fff; n += 2) {
			bus_write16(EUDASTL,n);
			_delay_us(10);
			if (bus_read16(EUDASTL) != n) {
				serial0_tx_PGM(PSTR("encx24j600: failed readback of value "));
				serial0_tx_dec(n);
				serial0_tx_cr();
				return;
			}
		}

		for (n = 1; n < 0x7fff; n += 2) {
			bus_write16(EUDANDL,n);
			_delay_us(10);
			if (bus_read16(EUDANDL) != n) {
				serial0_tx_PGM(PSTR("encx24j600: failed readback of value "));
				serial0_tx_dec(n);
				serial0_tx_cr();
				return;
			}
		}

		serial0_tx_PGM(PSTR("encx24j600: register access clean\r\n"));

		/* set up a write using one of the pointers into buffer space */
		bus_write16(EUDASTL,0x6000); /* disable entirely user buffer */
		bus_write16(EUDANDL,0x6001);
		bus_write16(EUDAWRPTL,0x0);
		bus_write16(EUDARDPTL,0x0);

		for (n = 0; n < 0x5fff; n++) {
			//x = rand();
			x = (n & 0xff);
			bus_write(EUDADATA,x);
			//_delay_us(1);
			y = bus_read(EUDADATA);
			if (y != x) {
				serial0_tx_PGM(PSTR("encx24j600: memory check failed at "));
				serial0_tx_dec(n);
				//serial0_tx_PGM(PSTR(" expected "));
				serial0_tx_cout(':');
				serial0_tx_dec(x);
				//serial0_tx_PGM(PSTR(" got "));
				serial0_tx_cout(':');
				serial0_tx_dec(y);
				serial0_tx_cr();
				return;
			}
		}
		serial0_tx_PGM(PSTR("enc242j600: memory check clean\r\n"));
	}
#endif

	/* Issue reset */
	bus_write(ECON2L+BITSET,ETHRST);
	_delay_ms(25);

	if ((bus_read(EUDASTL) | (bus_read(EUDASTH) << 8)) != (uint16_t) 0x0000) {
		serial0_tx_PGM(PSTR("encx24j600: reset failed, giving up\r\n"));
		return;
	}

#ifdef DEBUG_ENCX24J600 
	serial0_tx_PGM(PSTR("encx24j600: basic reset complete\r\n"));
#endif

	_delay_ms(25); /* wait for PHY? */

	/* section 8.6.1 MAC address */
	serial0_tx_PGM(PSTR("encx24j600: mac address is "));

	eth_mac[0] = bus_read(MAADR1L);
	serial0_tx_hex(eth_mac[0]);
	serial0_tx_cout(':');

	eth_mac[1] = bus_read(MAADR1H);
	serial0_tx_hex(eth_mac[1]);
	serial0_tx_cout(':');

	eth_mac[2] = bus_read(MAADR2L);
	serial0_tx_hex(eth_mac[2]);
	serial0_tx_cout(':');

	eth_mac[3] = bus_read(MAADR2H);
	serial0_tx_hex(eth_mac[3]);
	serial0_tx_cout(':');

	eth_mac[4] = bus_read(MAADR3L);
	serial0_tx_hex(eth_mac[4]);
	serial0_tx_cout(':');

	eth_mac[5] = bus_read(MAADR3H);
	serial0_tx_hex(eth_mac[5]);

	serial0_tx_cr();

	/* section 8.3 - RX buffer */
	bus_write16(ERXSTL,TX_BUF_SIZE);
	/* And make the next packet pointer start here */
	next_packet_ptr = TX_BUF_SIZE;
	/* And point the indirect read buffer pointer at this place */
	bus_write16(ERXRDPTL,TX_BUF_SIZE);
	/* And set the tx write pointer to the base address */
	bus_write16(EGPWRPTL,0x0000);

	/* init the TX slot data */
	{
		uint8_t n;

		for (n = 0; n < 4; n++) {
			tx_slot[n].busy = 0; /* clear busy flag */
			tx_slot[n].ready = 0; /* is not ready for transmit */
			/* length is cleared by allocation function */
		}
	}
	last_tx = 0xff; /* did not transmit a slot */

	/* 8.6 MAC init */
	bus_write16(MAMXFLL,1518); /* maximum frame is 1518 bytes long */
	//bus_write(ECON1L+BITSET,RXEN); /* enable receive */
	/* wait until PHY has link up */

	/* Enable multicast ethernet too, needed for mDNS and other protocols */
	bus_write(ERXFCONL+BITSET,MCEN);

	/* recieve status vectors */
	curr_rx_len = 0;
	curr_rx_len_left = 0;

	/* init PHY */
	/* FIXME: only does autoneg at the moment */
	encx24j600_phy_write(PHANA,0x05e1); /* 8.7 */

	/* enable our-side interrupts on falling edges */
	PORTB.DIRCLR = PIN4_bm; /* make it input */
	PORTB.PIN4CTRL = PORT_ISC_FALLING_gc; /* falling edge */
	PORTB.INT0MASK = PIN4_bm; /* pin4 -> portd int0 */
	PORTB.INTCTRL = PORT_INT0LVL_LO_gc; /* int0 is low prio */
	PMIC.CTRL |= PMIC_LOLVLEN_bm; /* make sure low prio enabled */
	/* and global senabled somewhere else */

	/* clear global interrupts for now */
	bus_write(EIEH+BITCLR,INTIE);
	/* enable interrupts for link state changes, and packets waiting */
	bus_write(EIEH+BITSET,LINKIE);
	bus_write(EIEL+BITSET,PKTIE);
	/* clear all flags */
	bus_write16(EIRL,0);
	/* enable interrupts */
	bus_write(EIEH+BITSET,INTIE);

#ifdef DEBUG_ENCX24J600
	if (PORTB.IN & PIN4_bm) {
		serial0_tx_PGM(PSTR("encx24j600: int high\r\n"));
	} else {
		serial0_tx_PGM(PSTR("encx24j600: int low\r\n"));
	}
#endif 

	return;
}

/* Test if there is a waiting packet in the RX queue */
uint8_t encx24j600_rx_waiting(void) {
	/* lower byte of ESTAT has current packet count */
	return (bus_read(ESTATL));
}

/* PHY read */
uint16_t encx24j600_phy_read(uint8_t addr) {
	uint8_t n = 0;

	/* PHY in indirect via MAC registers */

	/* write PHY register address */
	bus_write(MIREGADRL,addr);
	bus_write(MIREGADRH,1); /* see 3.3.1 */

	/* issue read */
	bus_write(MICMDL,MIIRD);

	/* wait for response */
	_delay_us(100);

	/* poll for compete */
	for (n = 0; n < 20; n++) {
		if (!(bus_read(MISTATL) & BUSY)) {
			break;
		}
		_delay_us(100);
	}

	if (n == 20) {
		serial0_tx_PGM(PSTR("encx24j600: PHY timeout\r\n"));
		return 0;
	}

	return bus_read16(MIRDL);

}

/* PHY write */
void encx24j600_phy_write(uint8_t addr, uint16_t value) {
	uint8_t n = 0;

	/* write target PHY register address */
	bus_write(MIREGADRL,addr);
	bus_write(MIREGADRH,1); /* see 3.3.2 */

	/* write happens when upper byte of MIWR is written.
    hard-coding order here since we must be sure of it */ 
	bus_write(MIWRL,(value & 0xff));
	bus_write(MIWRH,(value >> 8));

	/* wait for it to complete */
	_delay_us(100);

	/* poll for compete */
	for (n = 0; n < 20; n++) {
		if (!(bus_read(MISTATL) & BUSY)) {
			break;
		}
		_delay_us(100);
	}

	if (n == 20) {
		serial0_tx_PGM(PSTR("encx24j600: PHY timeout\r\n"));
	}

	return;
}

/* Check to see the current packet is good, this also ensures
   the next packet pointer is updated */
/* Note: rx_waiting MUST be called before this, or bad data will be used */
uint8_t encx24j600_rx_good(void) {
	uint8_t rsv[6], n, rx_stat = 0;

	/* rsv0, rsv1 */
	next_packet_ptr = (bus_read(ERXDATA) | bus_read(ERXDATA) << 8);

	/* length */
	curr_rx_len = (bus_read(ERXDATA) | bus_read(ERXDATA) << 8);
	curr_rx_len_left = curr_rx_len; /* ensure we don't try to read beyond end */

	/* remaining vectors, as named */
	for (n = 2; n < 6; n++) {
		rsv[n] = bus_read(ERXDATA);
	}

	/* return the status of this packet to the caller */
	if (rsv[2] & 0x80) { /* Recieved Ok Flag */
		rx_stat |= E_RX_GOOD;
	}
	if (rsv[2] & 0x10) { /* CRC Error flag */
		rx_stat |= E_RX_CRC;
	}
	if (rsv[2] & 0x40) { /* Length out of range */
		rx_stat |= E_RX_LONG;
	}
	if (rsv[2] & 0x20) { /* Has a type field */
		rx_stat |= E_RX_TYPE;
	}
	if (rsv[3] & 0x01) { /* Multicast */
		rx_stat |= E_RX_MULTICAST;
	}
	if (rsv[3] & 0x02) { /* Broadcast */
		rx_stat |= E_RX_BROADCAST;
	}
	if (rsv[3] & 0x40) { /* VLAN */
		rx_stat |= E_RX_VLAN;
	}

	/* return the status bits */
	return (rx_stat);
}

/* Complete the RX, advance the read pointer to the next packet 
   pointer, decrement the packet count */
void encx24j600_rx_complete(void) {
	/* update buf pointer to next packet */
	bus_write16(ERXRDPTL,next_packet_ptr);
	/* advance tail pointer to a couple of bytes back */
	if (next_packet_ptr - 2 < TX_BUF_SIZE)
		bus_write16(ERXTAILL,(next_packet_ptr-2)+0x5000); /* wraps at bottom to top */
	else {
		bus_write16(ERXTAILL,(next_packet_ptr-2)); /* normal */
	}
	/* and decrement the packet count */
	bus_write(ECON1H+BITSET,PKTDEC);
	/* clear RX counters */
	curr_rx_len = 0;
	curr_rx_len_left = 0;
}

/* RX read data from the packet, this advances up to but not beyond
   the remaining bytes left */
uint16_t encx24j600_rx(void *pkt, uint16_t len) {
	/* check to see we have something to read */
	if (!curr_rx_len_left) {
		serial0_tx_PGM(PSTR("encx24j600: BUG: tried to read packet data not present\r\n"));
		return 0; /* we read nothing */
	}

	if (curr_rx_len_left < len) {
		serial0_tx_PGM(PSTR("encx24j600: packet rx underrun\r\n"));
		len = curr_rx_len_left;
	}

	/* do a block read from the ERXDATA register */
	bus_read_block(ERXDATA,pkt,len);

	/* and account for that many bytes */
	curr_rx_len_left -= len;

	/* return bytes read */
	return len;
}

uint16_t encx24j600_rx_skip(uint16_t len) {
	/* advance the RX pointer directly */
	uint16_t next;

	if (curr_rx_len_left < len) {
		serial0_tx_PGM(PSTR("encx24j600: packet rx underrun\r\n"));
		len = curr_rx_len_left;
	}

	next = bus_read16(ERXRDPTL);
	next += len;
	if (next < TX_BUF_SIZE) {
		next += 0x5000;
	}
	/* write the new read pointer */
	bus_write16(ERXRDPTL,next);
	curr_rx_len_left -= len;

	return len;
}

/* perform a CRC check on RX data. This is assumed to be RX data just read
   so it's a *reverse* length. We don't nuke the checksum since this
   should sum to 0x0000 or 0xffff. */
uint16_t encx24j600_rx_crc(uint16_t len) {
	uint16_t start;
	/* prepare to do DMA */

	/* this is checksum only */
	bus_write(ECON1L+BITCLR,DMACPY);
	bus_write(ECON1L+BITCLR,DMANOCS);

	/* init from 0x0000 */
	bus_write(ECON1L+BITCLR,DMACSSD);

	/* the start is the read pointer minus the length */
	start = bus_read16(ERXRDPTL)-len;
	if (start < TX_BUF_SIZE) { /* we have wrapped below */
		start += 0x5000; /* back to top */
	}
	bus_write16(EDMASTL,start);
	bus_write16(EDMALENL,len);

	/* perform CRC calculation */
	bus_write(ECON1L+BITSET,DMAST);

	while (bus_read(ECON1L) & DMAST);

	return bus_read16(EDMACSL);
}

/* generate a CRC for as yet unread data. If payload exceeds available 
   data in the packet, it will throw a warning. This is used by the UDP
   code which needs to checksum its payload */
uint16_t encx24j600_rx_crc_unread(uint16_t payload, uint16_t seed) {
	uint16_t start;

	/* check this is sane first */
	if (payload > curr_rx_len_left) {
		serial0_tx_PGM(PSTR("encx24j600: requested CRC of more bytes than packet as left\r\n"));
		return 0;
	}

	/* prepare to do DMA */

	/* this is checksum only */
	bus_write(ECON1L+BITCLR,DMACPY);
	bus_write(ECON1L+BITCLR,DMANOCS);

	if (seed) {
		/* write the seed into the CRC output */
		bus_write16(EDMACSL,seed);
		/* and set the bit saying we like this number */
		bus_write(ECON1L+BITSET,DMACSSD);
	} else {
		/* init from 0x0000 */
		bus_write(ECON1L+BITCLR,DMACSSD);
	}

	/* the start is the read pointer minus the length */
	start = bus_read16(ERXRDPTL);
	if (start < TX_BUF_SIZE) { /* we have wrapped below */
		start += 0x5000; /* back to top */
	}
	bus_write16(EDMASTL,start);
	bus_write16(EDMALENL,payload);

	/* perform CRC calculation */
	bus_write(ECON1L+BITSET,DMAST);

	while (bus_read(ECON1L) & DMAST);

	return bus_read16(EDMACSL);

}

/* return the unread length of payload data from the current waiting packet. used in TCP code */
uint16_t encx24j600_rx_unread(void) {
	return curr_rx_len_left - 4; /* FIXME: er, why? */
}

/* Perform a random-access write to the TX buffer space */
/* returns nothing since this is a single byte write */
/* strictly speaking, this can write anywhere, but anyway.. */
void encx24j600_tx_write(uint16_t addr, uint8_t value) {
	/* (ab)use the rx write pointer, since it leaves
    the normal TX write pointer alone. */
	bus_write16(ERXWRPTL,addr);
	bus_write(ERXDATA,value);
} 

/* perform a write into an offset into a slot */
uint8_t encx24j600_tx_write_slot(uint8_t slot, uint8_t data, uint16_t offset) {
	uint16_t addr;

	if (slot > 3) {
		serial0_tx_PGM(PSTR("encx24j600: invalid slot\r\n"));
		return 0;
	}

	/* compute our start, this is based on the slot number multipled by the
    slot size, and then offset by the current length, and then the new
    length. */
	addr = ((slot+1)*TX_SLOT_SIZE)-tx_slot[slot].length+offset-1;

#ifdef DEBUG_ENCX24J600_SLOT
	serial0_tx_PGM(PSTR("s"));
	serial0_tx_dec(slot);
	serial0_tx_PGM(PSTR(" direct "));
	serial0_tx_hex(addr >> 8);
	serial0_tx_hex(addr & 0xff);
	serial0_tx_cr();
#endif

	if (addr >= (slot+1)*TX_SLOT_SIZE) {
		serial0_tx_PGM(PSTR("encx24j600: attempt to write to outside TX slot\r\n"));
		return 0;
	}

	bus_write16(ERXWRPTL,addr);
	bus_write(ERXDATA,data);

	return 1;
}

uint16_t encx24j600_tx_crc(uint16_t start, uint16_t len, uint16_t seed) {
	/* get the DMA engine to compute this */

	/* this is checksum only */
	bus_write(ECON1L+BITCLR,DMACPY);
	bus_write(ECON1L+BITCLR,DMANOCS);

	if (seed) {
		/* write the seed into the CRC output */
		bus_write16(EDMACSL,seed);
		/* and set the bit saying we like this number */
		bus_write(ECON1L+BITSET,DMACSSD);
	} else {
		/* init from 0x0000 */
		bus_write(ECON1L+BITCLR,DMACSSD);
	}

	/* set the range */
	bus_write16(EDMASTL,start);
	bus_write16(EDMALENL,len);

	/* do it! */
	bus_write(ECON1L+BITSET,DMAST);

	/* check to see when it's done, and return the value */
	while (bus_read(ECON1L) & DMAST);

	return bus_read16(EDMACSL);
}

/* crc and copy */
/* only used by ICMP! */
uint16_t encx24j600_tx_dmacrc(uint16_t len, uint16_t seed) {
	uint16_t dest;
	/* get the DMA engine to compute this */

	/* figure out where our dest is */
	dest = bus_read16(EGPWRPTL);

	/* this is checksum and copy */
	bus_write(ECON1L+BITSET,DMACPY);
	bus_write(ECON1L+BITCLR,DMANOCS);

	if (seed) {
		/* write the seed into the CRC output */
		bus_write16(EDMACSL,seed);
		/* and set the bit saying we like this number */
		bus_write(ECON1L+BITSET,DMACSSD);
	} else {
		/* init from 0x0000 */
		bus_write(ECON1L+BITCLR,DMACSSD);
	}

	/* set the range */
	bus_write16(EDMASTL,bus_read16(ERXRDPTL));
	bus_write16(EDMALENL,len);

	/* set destination for copy to current water mark for TX writes */
	bus_write16(EDMADSTL,dest);

	/* do it! */
	bus_write(ECON1L+BITSET,DMAST);

	/* check to see when it's done */
	while (bus_read(ECON1L) & DMAST);

	/* advance write pointer to start plus len, wrapped */
	bus_write16(EGPWRPTL,(dest+len)&(TX_BUF_SIZE-1));

	/* return CRC */
	return bus_read16(EDMACSL);
}

/* returns a free slot of the available ones on chip, 0xff is failure */
uint8_t encx24j600_tx_freeslot(void) {
	uint8_t n;
	for (n = 0; n < 4; n++) {
		if (tx_slot[n].busy == 0) {
			tx_slot[n].busy = 1;
			tx_slot[n].ready = 0;
			tx_slot[n].length = 0;
#ifdef DEBUG_ENCX24J600_SLOT_NOISY
			serial0_tx_PGM(PSTR("offered slot "));
			serial0_tx_dec(n);
			serial0_tx_cr();
#endif
			return n;
		}
	}
	return 0xff; /* failure! */
}

/* prepend some packet data into a slot */
uint8_t encx24j600_tx_prepend(uint8_t slot, uint8_t *pkt, uint16_t len) {
	uint16_t addr; /* computed start address */

	if (slot > 3) {
		serial0_tx_PGM(PSTR("encx24j600: invalid slot\r\n"));
		return 0;
	}

	/* compute our start, this is based on the slot number multipled by the
    slot size, and then offset by the current length, and then the new
    length. */
	addr = ((slot+1)*TX_SLOT_SIZE)-tx_slot[slot].length-len-1;

#ifdef DEBUG_ENCX24J600_SLOT
	serial0_tx_PGM(PSTR("s"));
	serial0_tx_dec(slot);
	serial0_tx_PGM(PSTR(" prepend "));
	serial0_tx_hex(addr >> 8);
	serial0_tx_hex(addr & 0xff);
	serial0_tx_cr();
#endif

	if (addr > TX_BUF_SIZE ||
			addr < (slot * TX_SLOT_SIZE)) {
		/* we have run out of space for this slot, reject */
		serial0_tx_PGM(PSTR("encx24j600: slot full, rejecting more data\r\n"));
		return 0;
	}

	/* bulk write the data into the slot */
	bus_write16(EGPWRPTL,addr);
	bus_write_block(EGPDATA,pkt,len);
	/* fixup our new length */
	tx_slot[slot].length += len;
	return len;
}

/* compute CRC on some part of the current slot (from current head) */
uint16_t encx24j600_tx_crc_slot(uint8_t slot, uint16_t len, uint16_t seed) {
	uint16_t addr; /* computed start address */

	if (slot > 3) {
		serial0_tx_PGM(PSTR("encx24j600: invalid slot\r\n"));
		return 0;
	}

	addr = ((slot+1)*TX_SLOT_SIZE)-tx_slot[slot].length-1;

#ifdef DEBUG_ENCX24J600_SLOT
	serial0_tx_PGM(PSTR("s"));
	serial0_tx_dec(slot);
	serial0_tx_PGM(PSTR(" crc "));
	serial0_tx_hex(addr >> 8);
	serial0_tx_hex(addr & 0xff);
	serial0_tx_cout(' ');
	serial0_tx_hex(len);
	serial0_tx_cr();
#endif

	/* DMA engine setup for CRC */
	/* checksum only */
	bus_write(ECON1L+BITCLR,DMACPY);
	bus_write(ECON1L+BITCLR,DMANOCS);

	if (seed) {
		/* write the seed into the CRC output */
		bus_write16(EDMACSL,seed);
		/* and set the bit saying we like this number */
		bus_write(ECON1L+BITSET,DMACSSD);
	} else {
		/* init from 0x0000 */
		bus_write(ECON1L+BITCLR,DMACSSD);
	}

	/* set the range to calculate over */
	bus_write16(EDMASTL,addr);
	bus_write16(EDMALENL,len);

	/* do it! */
	bus_write(ECON1L+BITSET,DMAST);

	/* check to see when it's done, and return the value */
	while (bus_read(ECON1L) & DMAST);

	return bus_read16(EDMACSL);
}

/* FIXME: error handling for full slot. Since ICMP is the only user 
   of this, and it's always at the end, it can't exceed a slot.. */
uint16_t encx24j600_tx_crcdma_slot(uint8_t slot, uint16_t len,
		uint16_t seed) {
	uint16_t addr;

	if (slot > 3) {
		serial0_tx_PGM(PSTR("encx24j600: invalid slot\r\n"));
		return 0;
	}

	/* destination of where we're copying into */
	/* slot top less exiting data less desired data */
	addr = ((slot+1)*TX_SLOT_SIZE)-tx_slot[slot].length-len-1;

#ifdef DEBUG_ENCX24J600_SLOT
	serial0_tx_PGM(PSTR("s"));
	serial0_tx_dec(slot);
	serial0_tx_PGM(PSTR(" dma "));
	serial0_tx_hex(addr >> 8);
	serial0_tx_hex(addr & 0xff);
	serial0_tx_cr();
#endif

	/* set up DMA engine to do this */
	bus_write(ECON1L+BITSET,DMACPY);
	bus_write(ECON1L+BITCLR,DMANOCS);

	if (seed) {
		/* write the seed into the CRC output */
		bus_write16(EDMACSL,seed);
		/* and set the bit saying we like this number */
		bus_write(ECON1L+BITSET,DMACSSD);
	} else {
		/* init from 0x0000 */
		bus_write(ECON1L+BITCLR,DMACSSD);
	}

	/* set the range */
	bus_write16(EDMASTL,bus_read16(ERXRDPTL));
	bus_write16(EDMALENL,len);

	/* set destination for copy to our computed start in the slot */
	bus_write16(EDMADSTL,addr);

	/* do it! */
	bus_write(ECON1L+BITSET,DMAST);

	/* check to see when it's done */
	while (bus_read(ECON1L) & DMAST);

	/* correct the length of the slot */
	tx_slot[slot].length += len;

	/* return CRC */
	return bus_read16(EDMACSL);
}

/* TX a completed slot. this does not wait for completed TX before
   returning */
/* FIXME: actually implement intertupt-handled TX */
void encx24j600_tx_slot(uint8_t slot) {
	uint16_t addr;

	if (slot > 3) {
		serial0_tx_PGM(PSTR("encx24j600: invalid slot\r\n"));
		return;
	}

	/* indicate what slot we last tried to transmit */
	tx_slot[slot].ready = 1;
	last_tx = slot;

	/* compute real start of the data in the slot */
	addr = ((slot+1)*TX_SLOT_SIZE)-tx_slot[slot].length-1;

#ifdef DEBUG_ENCX24J600_SLOT
	serial0_tx_PGM(PSTR("s"));
	serial0_tx_dec(slot);
	serial0_tx_PGM(PSTR(" tx "));
	serial0_tx_hex(addr >> 8);
	serial0_tx_hex(addr & 0xff);
	serial0_tx_cr();
#endif

	global_counters.eth_pkt_tx++;

	/* perform the TX */
	bus_write16(ETXSTL,addr);
	bus_write16(ETXLENL,tx_slot[slot].length);

	bus_write(ECON1L+BITSET,TXRTS);
	while (bus_read(ECON1L) & TXRTS) {
		_nop();
	}

	/* free the slot */
	tx_slot[slot].busy = 0;
	tx_slot[slot].ready = 0;

	return;

}

/* free a slot */
void encx24j600_slot_done(uint8_t slot) {
	if (slot > 3) {
		serial0_tx_PGM(PSTR("encx24j600: invalid slot\r\n"));
		return;
	}
	tx_slot[slot].busy = 0;
	tx_slot[slot].ready = 0;
	return;
}

/* returns the length of the payload of the given slot */
uint16_t encx24j600_slot_len(uint8_t slot) {
	if (slot > 3) {
		serial0_tx_PGM(PSTR("encx24j600: invalid slot\r\n"));
		return 0;
	}
	return tx_slot[slot].length;
}

/* Start to prepare a TX packet */
uint16_t encx24j600_tx_start(void) {
	/* for simplicity, we use whatever the current TX write pointer is at
    so we can buffer more than one packet. */
	return bus_read16(EGPWRPTL);
}

/* append data to an existing packet. This assumes we are writing 
   somewhere useful. Returns address of end point */
uint16_t encx24j600_tx_append(void *pkt, uint16_t len) {
	bus_write16(EGPWRPTL,bus_read16(EGPWRPTL)); /* re-init pointer? WTF */
	bus_write_block(EGPDATA,pkt,len);
	return (bus_read(EGPWRPTL) | bus_read(EGPWRPTH) << 8);
}

/* perform actual TX. This should really set up an interrupt to monitor
   success and advance a pointer when it works, ala the ringbuffer */

void encx24j600_tx_complete(uint16_t start, uint16_t len) {
	/* set the start point as passed */
	bus_write16(ETXSTL,start);
	bus_write16(ETXLENL,len);

	/* perform the tx */
	bus_write(ECON1L+BITSET,TXRTS);
	while (bus_read(ECON1L) & TXRTS) {
		_nop();
	}

	return;
}

/* process incoming interrupts */
void encx24j600_interrupt(void) {
	uint16_t intflags, spd;
	uint8_t stat;

	if (!(bus_read(ESTATH) & INT)) {
		return;
	}

	/* turn off global interrupt flag as per 13.0 */
	bus_write(EIEH+BITCLR,INTIE);

	/* read the interrupt flags and process */
	intflags = bus_read16(EIRL);

	/* since this is a bitmask, we have to compare it one at a time */
	if (intflags & (LINKIF << 8)) {
		/* link state has changed */
		bus_write(EIRH+BITCLR,LINKIF);
		/* work out what the new state is */
		stat = bus_read(ESTATH);
		if (stat & PHYLNK) {
			/* we have link, update the various required registers */
			serial0_tx_PGM(PSTR("encx24j600: link up ("));
			/* duplex propogated into MAC */
			if (stat & PHYDPX) {
				bus_write(MACON1L+BITSET,FULDPX);
				bus_write(MABBIPGL,0x15); /* 8.9 for FD */
			} else {
				bus_write(MACON1L+BITCLR,FULDPX);
				bus_write(MABBIPGL,0x12); /* 8.9 for HD */
			}
			/* now discover link speed etc */
			spd = (encx24j600_phy_read(PHSTAT3) >> 2) & 0x7;
			switch (spd) {
				case 1:
					eth_linkstate = E_LINK_10;
					break;
				case 2:
					eth_linkstate = E_LINK_100;
					break;
				case 5:
					eth_linkstate = E_LINK_10FD;
					break;
				case 6:
					eth_linkstate = E_LINK_100FD;
					break;
			}
			encx24j600_print_linkstate();
			serial0_tx_PGM(PSTR(")\r\n"));
			/* and now allow RX */
			bus_write(ECON1L+BITSET,RXEN);
			set_flag(flag_ether,FLAG_ETHER_LINK);
			set_flag(flag_ether,FLAG_ETHER_LINK_EVENT);
		} else {
			/* no link state, shut down RX */
			bus_write(ECON1L+BITCLR,RXEN);
			serial0_tx_PGM(PSTR("encx24j600: link down\r\n"));
			eth_linkstate = 0;
			clear_flag(flag_ether,FLAG_ETHER_LINK);
			set_flag(flag_ether,FLAG_ETHER_LINK_EVENT);
		}
	}

	if (intflags & (PKTIF)) {
		/* we have waiting packets, flag global loop to process */
		set_flag(flag_ether,FLAG_ETHER_RX);
		//bus_write(EIRL+BITCLR,PKTIF);
	}

	/* done processing interrupts, please re-enable them */
	bus_write(EIEH+BITSET,INTIE);

	return;
}

/* fluff to display current link state */
void encx24j600_print_linkstate(void) {
	switch (eth_linkstate) {
		case E_LINK_10:
			serial0_tx_PGM(PSTR("10M Half"));
			break;
		case E_LINK_100:
			serial0_tx_PGM(PSTR("100M Half"));
			break;
		case E_LINK_10FD:
			serial0_tx_PGM(PSTR("10M Full"));
			break;
		case E_LINK_100FD:
			serial0_tx_PGM(PSTR("100M Full"));
			break;
	}
	return;
}

/* bus implementation - actual code */

/* This implements an Intel-style 9-bit Address/8-bit data bus */
/* Pins and ports used are defined below */

/* Bus pin defintions */

/* LSB port - Assumed to be fully allocated to the LSB, and wired
   such that bit 0 is pin 0. This doubles as both Address and
   bi-directional Data */
#define BUS_LSB_PORT PORTA

/* 9th Address bit, this is output only since data is 8-bit */
#define BUS_MSB_PORT PORTB
#define BUS_MSB_PIN PIN3_bm

/* where are all our control lines */
#define BUS_CONTROL_PORT PORTB
/* Address latch */
#define BUS_AL_PIN PIN0_bm
/* Write strobe */
#define BUS_WR_PIN PIN1_bm
/* Read strobe */
#define BUS_RD_PIN PIN2_bm

uint16_t laddr = 0xffff; /* last address queried, global since AL is per-chip */

void bus_init(void) {
	/* initalise the various ports and elements to known states */

	/* AL, RD, and WR are outputs */
	BUS_CONTROL_PORT.DIRSET = BUS_AL_PIN | BUS_WR_PIN | BUS_RD_PIN;
	/* all desserted */
	BUS_CONTROL_PORT.OUTCLR = BUS_AL_PIN | BUS_WR_PIN | BUS_RD_PIN;

	/* Reset address/data mux */
	BUS_LSB_PORT.DIR = 0xff; /* all output */
	BUS_LSB_PORT.OUT = 0;

	/* MSB PIN is always output, just toggled */
	BUS_MSB_PORT.DIRSET = BUS_MSB_PIN;
	BUS_MSB_PORT.OUTCLR = BUS_MSB_PIN;

	/* You may wish to handle CS too? */

}

/* read a single byte from an address on the bus */
uint8_t bus_read(uint16_t addr) {
	//inline uint8_t __attribute__((always_inline)) bus_read(uint16_t addr) {
	uint8_t value;

	if (addr != laddr) {
		/* write LSB of address to pins */
		BUS_LSB_PORT.DIRSET = 0xff; /* these are outputs now */
		BUS_LSB_PORT.OUT = (addr & 0xff);

		/* we have a single upper bit, apply it, always output */
		if ((addr >> 8) & 0x1) {
			BUS_MSB_PORT.OUTSET = BUS_MSB_PIN;
		} else {
			BUS_MSB_PORT.OUTCLR = BUS_MSB_PIN;
		}

		/* strobe AL, we assume it's an output here */
		BUS_CONTROL_PORT.OUTSET = BUS_AL_PIN;
		/* we need at least one wait state */
		_nop(); _nop();
		BUS_CONTROL_PORT.OUTCLR = BUS_AL_PIN;

		laddr = addr; /* update cache */
	}

	/* bus should be input now */
	BUS_LSB_PORT.DIR = 0; /* all input */

	/* raise read, and nop to ensure it's on the bus */
	BUS_CONTROL_PORT.OUTSET = BUS_RD_PIN;
	_nop(); _nop(); /* should be enough to cover the pin read delay */
	/* store the current LSB, it's the 8-bit data */
	value = BUS_LSB_PORT.IN;

	/* lower read */
	BUS_CONTROL_PORT.OUTCLR = BUS_RD_PIN;

	return value;
}

//inline void __attribute__((always_inline)) bus_read_block(uint16_t addr, void *buf, uint16_t len) {
void bus_read_block(uint16_t addr, void *buf, uint16_t len) {
	if (addr != laddr) {
		/* write LSB of address to pins */
		BUS_LSB_PORT.DIRSET = 0xff; /* these are outputs now */
		BUS_LSB_PORT.OUT = (addr & 0xff);

		/* we have a single upper bit, apply it, always output */
		if ((addr >> 8) & 0x1) {
			BUS_MSB_PORT.OUTSET = BUS_MSB_PIN;
		} else {
			BUS_MSB_PORT.OUTCLR = BUS_MSB_PIN;
		}

		/* strobe AL, we assume it's an output here */
		BUS_CONTROL_PORT.OUTSET = BUS_AL_PIN;
		/* we need at least one wait state */
		_nop(); _nop();
		BUS_CONTROL_PORT.OUTCLR = BUS_AL_PIN;

		/* clear the MSB pin */
		BUS_MSB_PORT.OUTCLR = BUS_MSB_PIN;

		laddr = addr; /* update cache */
	}

	/* bus should be input now */
	BUS_LSB_PORT.DIR = 0; /* all input */

	while (len--) {
		BUS_CONTROL_PORT.OUTSET = BUS_RD_PIN;
		_nop(); _nop();
		*(uint8_t *)(buf++) = BUS_LSB_PORT.IN;
		BUS_CONTROL_PORT.OUTCLR = BUS_RD_PIN;
	}
	return;
}

uint16_t bus_read16(uint16_t addr) {
	//inline uint16_t __attribute__((always_inline)) bus_read16(uint16_t addr) {
	return (bus_read(addr & 0xfffe)) | (bus_read((addr & 0xfffe)+1) << 8);
}

void bus_write(uint16_t addr, uint8_t value) {
	//inline void __attribute__((always_inline)) bus_write(uint16_t addr, uint8_t value) {
	if (addr != laddr) {
		/* write LSB of address to pins */
		BUS_LSB_PORT.DIR = 0xff; /* these are outputs now */
		BUS_LSB_PORT.OUT = (addr & 0xff);

		/* we have a single upper bit, apply it */
		if ((addr >> 8) & 0x1) {
			BUS_MSB_PORT.OUTSET = BUS_MSB_PIN;
		} else {
			BUS_MSB_PORT.OUTCLR = BUS_MSB_PIN;
		}

		/* strobe AL, we assume it's an output here */
		BUS_CONTROL_PORT.OUTSET = BUS_AL_PIN;
		/* we need at least one wait state */
		_nop(); _nop();
		BUS_CONTROL_PORT.OUTCLR = BUS_AL_PIN;

		/* clear the MSB pin */
		BUS_MSB_PORT.OUTCLR = BUS_MSB_PIN;

		laddr = addr; /* update cache */
	}

	BUS_LSB_PORT.DIR = 0xff; /* all output */

	/* and then write the data */
	BUS_LSB_PORT.OUT = value;

	/* now issue the write */
	BUS_CONTROL_PORT.OUTSET = BUS_WR_PIN;
	_nop(); _nop(); /* 1 wait state */
	BUS_CONTROL_PORT.OUTCLR = BUS_WR_PIN;

	/* clear them now we're done with the write */
	BUS_LSB_PORT.OUT = 0x0;

}

//inline void __attribute__((always_inline)) bus_write_block(uint16_t addr, void *buf, uint16_t len) {
void bus_write_block(uint16_t addr, void *buf, uint16_t len) {
	if (addr != laddr) {
		/* write LSB of address to pins */
		BUS_LSB_PORT.DIR = 0xff; /* these are outputs now */
		BUS_LSB_PORT.OUT = (addr & 0xff);

		/* we have a single upper bit, apply it, always output */
		if ((addr >> 8) & 0x1) {
			BUS_MSB_PORT.OUTSET = BUS_MSB_PIN;
		} else {
			BUS_MSB_PORT.OUTCLR = BUS_MSB_PIN;
		}

		/* strobe AL, we assume it's an output here */
		BUS_CONTROL_PORT.OUTSET = BUS_AL_PIN;
		/* we need at least one wait state */
		_nop(); _nop();
		BUS_CONTROL_PORT.OUTCLR = BUS_AL_PIN;

		laddr = addr; /* update cache */

		/* clear the MSB pin */
		BUS_MSB_PORT.OUTCLR = BUS_MSB_PIN;

	}

	BUS_LSB_PORT.DIR = 0xff; /* make sure they're output for write */

	/* write the block */
	while (len--) {
		/* this sets the LSB on the wire and advances to next byte in buf */
		BUS_LSB_PORT.OUT = *(uint8_t *)(buf++);
		/* strobe WR to tell chip to pick it up */
		BUS_CONTROL_PORT.OUTSET = BUS_WR_PIN;
		_nop(); _nop(); /* 1 wait state */
		BUS_CONTROL_PORT.OUTCLR = BUS_WR_PIN;
		_nop(); _nop();
	}

	/* clear them now we're done with the write */
	BUS_LSB_PORT.OUT = 0x0;

}

//inline void __attribute__((always_inline)) bus_write16(uint16_t addr, uint16_t value) {
void bus_write16(uint16_t addr, uint16_t value) {
	bus_write(addr & 0xfffe,value & 0xff);
	bus_write((addr & 0xfffe)+1, value >> 8);
}

void encx24j600_eeprom_init(void) {
	return;
}
